/**
 * @brief 日志模块封装
 *
 * */

#ifndef __YUECHUAN_LOG_H__
#define __YUECHUAN_LOG_H__

#include <string>
#include <cstdint>
#include <memory>
#include <list>
#include <sstream>
#include <fstream>
#include <iostream>
#include <vector>
#include <cstdarg>
#include <map>
#include "singleton.h"
#include "util.h"
#include "thread.h"

#define YUECHUAN_LOG_LEVEL(logger, level) \
    if(logger->getLevel() <= level) \
        yuechuan::LogEventWrap(yuechuan::LogEvent::ptr(new yuechuan::LogEvent(logger, level, \
                        __FILE__, __LINE__, 0, yuechuan::GetThreadId(),\
                yuechuan::GetFiberId(), time(0), yuechuan::Thread::GetName()))).getSS()
#define YUECHUAN_LOG_DEBUG(logger) YUECHUAN_LOG_LEVEL(logger, yuechuan::LogLevel::DEBUG)
#define YUECHUAN_LOG_INFO(logger) YUECHUAN_LOG_LEVEL(logger, yuechuan::LogLevel::INFO)
#define YUECHUAN_LOG_WARN(logger) YUECHUAN_LOG_LEVEL(logger, yuechuan::LogLevel::WARN)
#define YUECHUAN_LOG_ERROR(logger) YUECHUAN_LOG_LEVEL(logger, yuechuan::LogLevel::ERROR)
#define YUECHUAN_LOG_FATAL(logger) YUECHUAN_LOG_LEVEL(logger, yuechuan::LogLevel::FATAL)

#define YUECHUAN_LOG_FMT_LEVEL(loogger, level, fmt, ...) \
    if (logger->getLevel() <= level)                     \
    yuechuan::LogEventWrap(yuechuan::LogEvent::ptr(new yuechuan::LogEvent(logger, level, __FILE__, __LINE__, 0, yuechuan::GetThreadId(), yuechuan::GetFiberId(), time(0), yuechuan::LogLevel::toString(level)))).getEvent()->format(fmt, __VA_ARGS__)
#define YUECHUAN_LOG_FMT_INFO(logger, fmt, ...) YUECHUAN_LOG_FMT_LEVEL(logger, yuechuan::LogLevel::INFO, __VA_ARGS__)
#define YUECHUAN_LOG_FMT_WARN(logger, fmt, ...) YUECHUAN_LOG_FMT_LEVEL(logger, yuechuan::LogLevel::WARN, __VA_ARGS__)
#define YUECHUAN_LOG_FMT_ERROR(logger, fmt, ...) YUECHUAN_LOG_FMT_LEVEL(logger, yuechuan::LogLevel::ERROR, __VA_ARGS__)
#define YUECHUAN_LOG_FMT_FATAL(logger, fmt, ...) YUECHUAN_LOG_FMT_LEVEL(logger, yuechuan::LogLevel::FATAL, __VA_ARGS__)

#define YUECHUAN_LOG_ROOT() yuechuan::LoggerMgr::GetInstance()->getRoot()

#define YUECHUAN_LOG_NAME(name) yuechuan::LoggerMgr::GetInstance()->getLogger(name)
namespace yuechuan {
    class Logger;

    /**
     * @brief 日志等级
     */
    class LogLevel {
    public:
        enum Level {
            UNKNOW = 0, /// 未知级别
            DEBUG = 1, /// Debug级别
            INFO = 2,  /// Info级别
            WARN = 3,  /// Warn级别
            ERROR = 4, /// Error级别
            FATAL = 5  /// Fatal级别
        };

        /**
         * @brief 将日志级别转为文本输出
         * @param level 日志级别
         */
        static const char *toString(LogLevel::Level level);

        /**
         * @brief 从字符串转化为日志等级
         * @param str 含有上面枚举值的字符串
         * @return LogLevel::Level 事件等级
         */
        static LogLevel::Level FromString(const std::string &str);
    };

    /**
     * @brief 日志事件
     * @details 包括文件名称，具体行号，事件等级，要执行事件的日志器， 触发该事件的线程，协程，触发时间
     */
    class LogEvent {
    public:
        typedef std::shared_ptr<LogEvent> ptr;

        /**
         *
         * @brief 构造函数
         * @param logger 日志器
         * @param level 日志级别
         * @param file 文件名
         * @param line 行号
         * @param elapse 程序启动依赖的耗时（毫秒）
         * @param thread_id 线程ID
         * @param fiber_id 协程ID
         * @param time 日志时间（秒）
         * @param thread_name 线程名
         * */
        LogEvent(std::shared_ptr<Logger> logger, LogLevel::Level level, const char *file,
                 int32_t m_line, uint32_t elapse, uint32_t thread_id, uint32_t fiber_id,
                 uint64_t time, std::string threaName);

        /**
         * @brief 获取文件名称
         * @return const char* 文件名称
         */
        const char *getFile() const { return m_file; }

        /**
         * @brief 获取需要打印日志的行号
         * @return int32_t 文件行号
         */
        int32_t getLine() const { return m_line; }

        /**
         * @brief 获取程序启动到日志输出的毫秒数
         * @return uint32_t 毫秒数
         */
        uint32_t getElapse() const { return m_elapse; }

        /**
         * @brief 获取触发日志事件的线程ID
         * @return uint32_t 线程ID
         */
        uint32_t getThreadId() const { return m_threadId; }

        /**
         * @brief 获取触发日志事件的协程ID
         * @return uint32_t 协程ID
         */
        uint32_t getFiberId() const { return m_fiberId; }

        /**
         * @brief 获取日志触发的事件
         * @return uint64_t 日志触发时间
         */
        uint64_t getTime() const { return m_time; }

        /**
         * @brief 获取日志消息
         * @return std::string 日志正文消息
         */
        std::string getContent() { return m_ss.str(); }

        /**
         * @brief 获取日志消息流
         * @return std::stringstream string流
         */
        std::stringstream &getSS() { return m_ss; }

        /**
         * @brief 获取日志器
         * @return std::shared_ptr<Logger> 日志发送器
         */
        std::shared_ptr<Logger> getLogger() const { return m_logger; }

        /**
         * @brief 获取日志等级
         * @return LogLevel::Level enum 日志等级
         */
        LogLevel::Level getLevel() const { return m_level; }

        /**
         * @brief 获取触发日志事件的线程名称
         * @return std::string 线程名称
         */
        std::string getThreadName() const { return m_threadName; }

        /**
         * @brief 设置日志输出格式
         * @param fmt 格式化字符串
         * @param ...
         */
        void format(const char *fmt, ...);

        /**
         * @brief 设置日志输出格式
         * @param fmt 格式化字符串
         * @param al 指向参数变量的指针
         */
        void format(const char *fmt, va_list al);

    private:
        const char *m_file = nullptr;        ///文件名
        int32_t m_line = 0;                  /// 行号
        uint32_t m_elapse = 0;               /// 程序启动到日志输出的毫秒数
        uint32_t m_threadId = 0;             /// 线程ID
        uint32_t m_fiberId = 0;              /// 协程ID
        uint64_t m_time;                     ///日志触发时间
        std::stringstream m_ss;              ///日志消息内容
        std::shared_ptr<Logger> m_logger;   /// 日志器
        LogLevel::Level m_level;            ///日志等级
        std::string m_threadName;           /// 线程名
    };

    /**
     * @brief 控制LogEvent事件包装
     *
     */
    class LogEventWrap {
    public:
        /**
         * @brief 构造函数
         * @param e 日志事件
         */
        LogEventWrap(LogEvent::ptr e);

        /**
         * @brief 析构函数
         */
        ~LogEventWrap();

        /**
         * @brief 获取事件内容流
         * @return std::stringstream& 事件内容流的引用
         */
        std::stringstream &getSS();

    private:
        LogEvent::ptr m_event;          /// 封装的事件指针
    };

    /**
     * @brief 日志格式化
     * @details 根据传入的格式化字符串来创建格式化对象
     */
    class LogFormatter {
    public:
        typedef std::shared_ptr<LogFormatter> ptr;

        /**
         * @brief 构造函数
         * @param pattern 格式化模板
         * @details 默认格式为 "%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
         *           年-月-日 时:分:秒 \t 线程ID \t 线程名 \t 协程ID \t [日志级别] \t [日志名称] \t 文件名称: 行号 \t 消息 \t 换行
         */
        LogFormatter(const std::string &pattern);

        /**
         * @brief 根据传入的日志，日志等级，日志事件输出日志字符串
         * @param logger 日志器对象指针
         * @param level 日志等级
         * @param event 日志事件
         * @return std::string 格式化输出的字符串
         */
        std::string format(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event);

        /**
         * @brief 获取日志的流，传入输出流，日志器，日志等级，日志事件
         * @param os  输出流
         * @param logger 日志器
         * @param level 日志等级
         * @param event 日志事件
         * @return 获取格式化流
         */
        std::ostream &
        format(std::ostream &os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event);

        /**
         * @brief 获取格式化输出的模式
         * @return std::string 格式化输出的模式
         */
        std::string getPattern() const { return m_pattern; }

    public:
        /**
         * @brief 日志格式化元素
         */
        class FormatItem {

        public:
            typedef std::shared_ptr<FormatItem> ptr;

            virtual void format(std::ostream &os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) = 0;
        };

        bool isError() { return m_error; }

    private:
        /**
         * @brief 格式化字符串进行解析
         */
        void init();
        /// 格式化输出的模式
        std::string m_pattern;
        /// 要执行format的对象数组
        std::vector<FormatItem::ptr> m_items;
        /// 判断该格式是否错误
        bool m_error = false;
    };

    /**
     * @brief 日志输出地
     * @details 设置日志输出的地点，可以是控制台，yaml，(通过继承的方式，重写其log方法)
     *          需要配置日志器，日志格式化对象，日志事件，日志等级
     */
    class LogAppender {
        friend class Logger;

    public:
        typedef std::shared_ptr<LogAppender> ptr;
        typedef Mutex MutexType;
        virtual ~LogAppender() {}

        /**
        * @brief 输出纯虚函数，接口函数，自类继承需要重写该方法
        * @param logger 输出的日志器，作用得到其名称和输出地，日志等级
        * @param level  输出到当前地点的日志等级 （ 可能针对不同的输出地，同一个日志事件等级不同)
        * @param event  日志事件
        */
        virtual void log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) = 0;

        /**
         * @brief 设置日志输出地的日志格式化对象
         * @param formatter 日志格式化对象的指针
         */
        void setLogFormatter(LogFormatter::ptr formatter);

        /**
         * @brief 设置日志输出等级
         * @param level 日志等级
         */
        void setLevel(LogLevel::Level level) { m_level = level; }

        /**
         * @brief 获取日志输出地的日志格式化对象
         * @return LogFormatter::ptr 日志格式化对象智能指针
         */
        LogFormatter::ptr getLogFormatter();

        /**
         * @brief 纯虚函数，将日志输出到yaml文件中
         * @return
         */
        virtual std::string toYamlString() = 0;

    protected:
        /// 日志等级，默认为DEBUG
        LogLevel::Level m_level = LogLevel::DEBUG;
        /// 日志格式化对象
        LogFormatter::ptr m_formatter;
        /// 日志输出地是否有格式化对象
        bool m_hasFormatter = false;
        /// 经常要执行写操作，需要加锁
        MutexType m_mutex;
    };

    /**
     * @brief 日志器
     * @details 用于接收一个log事件，然后将其进行输出到指定的地点
     */
    class Logger : public std::enable_shared_from_this<Logger> {
        friend class LoggerManager;


    public:
        typedef std::shared_ptr<Logger> ptr;
        typedef Mutex MutexType;

        Logger(const std::string &name = "root");

        void log(LogLevel::Level level, LogEvent::ptr event);

        void debug(LogEvent::ptr event);

        void info(LogEvent::ptr event);

        void warn(LogEvent::ptr event);

        void error(LogEvent::ptr event);

        void fatal(LogEvent::ptr event);

        void addAppender(LogAppender::ptr appender);

        void delAppender(LogAppender::ptr appender);

        void clearAppender();

        void setLogFormatter(const LogFormatter::ptr &formatter);

        void setLogFormatter(const std::string &formatter);

        LogLevel::Level getLevel() const { return m_level; }

        void setLevel(LogLevel::Level level) { m_level = level; };

        const std::string &getName() const { return m_name; }

        LogFormatter::ptr getLogFormatter() const { return m_formatter; }

        std::string toYamlString();

    private:
        /// 日志名称
        std::string m_name;
        /// 日志等级
        LogLevel::Level m_level;
        /// 输出地的列表
        std::list<LogAppender::ptr> m_appenders;
        /// 日志器对象
        LogFormatter::ptr m_formatter;
        /// 默认日志器指针
        Logger::ptr m_root;
        /// 对于日志器，可能对于输出地列表进行读写，所以要加锁
        MutexType m_mutex;

    };

    //输出到控制台的Appender
    class StdoutLogAppender : public LogAppender {
    public:
        typedef std::shared_ptr<StdoutLogAppender> ptr;

        void log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override;

        std::string toYamlString() override;
    };

    // 输出到文件的Appender
    class FileLogAppender : public LogAppender {
    public:
        typedef std::shared_ptr<FileLogAppender> ptr;

        FileLogAppender(const std::string &filename);

        void log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override;

        bool reopen(); // 判断是否流已经打开

        std::string toYamlString() override;

    private:
        /// 要输出的文件名
        std::string m_filename;
        /// out文件流
        std::ofstream m_fileOstream;
        /// 重新打开文件的最后时间
        uint64_t m_lastTime = 0;
    };

    /**
     * @brief 日志器管理类
     */
    class LoggerManager {
    public:
        typedef Mutex MutexType;
        /**
         * @brief 构造函数
         */
        LoggerManager();

        Logger::ptr getLogger(const std::string &name);

        Logger::ptr getRoot() const { return m_root; }

        std::string toYamlString();

        /**
         * @brief 创建主日志器，默认为控制台输出
         */
        void init();

    private:
        MutexType m_mutex;
        /// 日志器和日志器名称进行绑定
        std::map<std::string, Logger::ptr> m_logger;
        /// 默认的日志器
        Logger::ptr m_root;
    };

    typedef yuechuan::Singleton<LoggerManager> LoggerMgr;
}

#endif
